|
|
| Have You Played Atari Today? | 2600 | 5200 | 7800 | Lynx | Jaguar | Forums | Store |
Leprechaun has gotten stalled unfortunately, but I came up with an idea which I'd like some comments on. One suggestion was to speed up the game. In the current WIP the player moves 1 pixel every 6 frames, while the enemies use fractional positioning to move at a slower rate. My initial idea was to change the player to use fractional positioning to move faster, but then change the enemies to 1 pixel every 6 frames (i.e. same as the current player). Anyway, it occurred to me that an interesting option would be to have the player & enemy movement speeds controlled by the difficulty switches - player slow/fast, enemy fast/slow. So a beginner could set the player to fast and the enemies to slow and get a major advantage while the expert could set the player to slow and the enemies to fast so both would be moving at the same 1 pixel per 6 frame rate. Hmm... but wouldn't that make comparing scores invalid? What if the difficulty level somehow also impacted the score? Say if the difficulty was set to easiest then the level timer would count down faster than if it was set to hardest? Now, I don't think the code could handle having the difficulty switches changed in mid-level, or at least in mid-life since it would mess up the fractional positioning. But it should be possible to read the switches as part of the start-of-life pause, so a player could change the difficulty more dynamically. Comments?
Okay, I've figured out how the clock for the 4 stage shifter is done and gone back through and reworked all of the equations now that I'm a little more used to keeping track of whether I'm changing a NOR to an OR or an AND (depending on whether I want the output or the inputs inverted). This isn't to say I couldn't get it wrong, just less chance. Q1-5 is the 5 stage shift register Q6-9 is the 4 stage shift register CODE AUDC Q1in 0000 1 xx00 (Q5 XOR Q9) + !Q1*!Q2*!Q3*!Q4*!Q5*!Q6*!Q7*!Q8*!Q9 yyxx (Q5 XOR Q3) + !Q1*!Q2*!Q3*!Q4*!Q5 where xx = [01,10,11] and yy = [00,01,10,11] (Whaddya know, I got it right.) Okay, the big AND term OR'd to the XOR is just a reset in case the shift registers get stuck. AUDC=0000 is easy to understand and matches up with what we know about TIA audio - always high. AUDC=xx00 links the two shift registers, while AUDC=yyxx decouples the two registers. It should be noted that the 5 stage shifter feeds the 4 stage shifter, not the other way around. Next is the clock for the 4 stage shifter. For those that care, the 4 stage shifter is clocked on the falling edge of T02 at the same time the 5 stage shifter is latched. So the T01 clock can be ignored unless you are doing hyper-accurate emulation of AUDC change timing (since the 5 stage shifter inputs are latched on the falling edge of T01). CODE AUDC Q6-9 clk yy0y T02 yy10 T02 * !Q1*!Q2*!Q3*Q4 yy11 T02 * Q5 The first is easy to understand, the clocks for the two shifters are the same. AUDC=yy11 means that the output of the 5 stage shifter is used as the clock of the 4 stage shifter. AUDC=yy10 creates the divide by 31 clock. Finally, the 4 stage shifter. I just realized that the inputs to the set/reset registers are active low, i.e. inverted. Which now makes the Z1 input !Z1 which then corresponds to the "pure" outputs. Oh drat, that means Z2in = !Z1 etc. Ahh, they even labeled the "real" positive outputs. So I've fixed my equations to reflect that. CODE AUDC Q6in 0000 1 00xx (Q8 XOR Q9) + !Q6*!Q7*!Q8*!Q9 01yy !Q6 10yy Q5 11yy Q6*!Q7*Q8+!Q8 This reveals the last bits of info, including the divide by three logic. No, I haven't created the test program to validate my equations to the known ones yet. All things in good time.
Continuing my adventure puzzling out the TIA schematic, I turn my attention to the AUDF section as a prelude to figuring out how the clock signal for the 4 stage shifter is generated. The Audio Frequency Divider takes two clock signals (A01 and A02)* and generates two clock signals (T01 and T02)* using AUDF[0..4]. So to start with we need to look back at page 1 of the schematic and Andrew Tower's TIA Hardware Notes to learn more about where A01 and A02 come from. A01 and A02 are based on the main horizontal sync counter / LFSR which operates at colorburst / 4. A01 is active at counter reset (228->0) and LRHB (72), while A02 is active at RHS (32) and CNT (114). This is interesting because it means the audio clock isn't symetrical. Now, I don't know whether this will significantly change the output because the additional harmonics this creates will be very high, probably above the range of human hearing and probably filtered out as well. But I'll keep it in mind. The actual divider section had me scratching my head for a bit. I knew what it did but not how it did it. Part of the problem was I thought it was a down counter which reloaded the AUDF value when it hit zero. Then I realized that the feedback loop reset the counter to zero, which means it's an up counter and the bizarre FET construct on on the left side of the adder is a comparator. This also yeilds an important insight - because the counter resets to zero only when the value of AUDF is reached (well, one cycle after actually) if you change to a lower AUDF value (higher frequency), there is a chance there will be a full 32 cycle (16 scanline or 1ms) delay between output clocks because the counter has to wrap around. The T01 and T02 signals can now be determined: T02 = A02 * (counter=AUDF) T01 = A01 * (counter=AUDF+1) (although the counter is reset to zero at the same time). Yes, T01 happens after T02, even though it's used for the front latches in the 5 stage registers. Again, I'm not sure how much impact this will have in the end (the 4 stage shifter clock is driven by T02), but I will keep it in mind as I go forward. Now that I've got T01 and T02 locked down, there's two chunks of logic which I can now work out. First is Q1, which is used in the secondary feedback loop and is driven by T01. And finally the clock input to the 4 stage shifter. * Those zeros are actually phis, but I can't embed the HTML codes.
I have been looking at the TIA schematics to better understand how the "Audio Noise Generator" works. (The noise generator is on page 4, although there are details of the register blocks on page 1.) Really the ANG is three separate chunks. 1. AUDF - which divides down the 2 * HSYNC clock signal and creates the T01 and T02 clock signals. I haven't looked at this section in detail yet, although there may be some interesting bits around the timing of changing AUDF. I'm also going to assume that T01 and T02 are 1/4 clock signals 180 degress out of phase with each other. 2. AUDC - which drives the two LFSR sections. This is what I'm looking at now. It certainly is more complex than some of the table descriptions have suggested. One interesting item is there doesn't appear to be any way to bypass the 4 stage shift register completely. So changes in AUDC require some time to propogate all the way to the output. 3. AUDV - this is a simple ADC. Nothing really exciting other than any AUDF updates affect the output volume immediately. The part of AUDC which has been driving me nuts is the wired-NORs. Unlike some similar constructs elsewhere in the schematics, there is a small arrow attached to each wired AND. I finally puzzled this out be looking at the main 4 input NOR+NOT which is the feedback loop to the 5 stage shift register. Since this is an OR, any high input will result in a high output. If you track back the first vertical wired-NOR you see it is attached to the Q outputs of the AUDC (L type) register. We know that AUDC=%0000 triggers an always high output. Thus, the output of that wired NOR must be 1 when, and only when, AUDC=%0000, i.e. NOR logic. My next challenges are to figure out how the FETs attached to T01 at the output of the 5 stage shift register affect things and why there's a tri-state inverter attached to the clock input of the 4 stage shift register and how it works.
The Apple ][ was capable of "280x192 6 color hi-res graphics", but how it accomplished those graphics is bizzare to anyone familiar with modern memory mapped graphics (i.e. 256 color VGA). Even more amazing was the quality of games which were written for the Apple ][ series in spite of what the programmers must have gone through. In conventional memory mapped graphics there are two basic assumptions: 1. Each pixel is represented by a set number of bits, e.g. 1 for monochrome, 2 for 4 color, 4 for 16 color & 8 for 256 color. 2. Memory addressing typically follows the formula: Address = BaseAddress + Y * bytes/line + X * bits/pixel / 8 where bytes/line may or may not be equal to Xmax * bits/pixel / 8 Allegedly the great Woz realized he could reduce the number of chips required to produce hi-res graphics if assumption #2 was not followed. Thus, on the Apple ][ the formula for determining the address of the pixel containing the byte is: Address = BaseAddress + (Y & $07)<<10 + (Y & $38)<<4 + (Y & $40) + (Y & $C0)>>2 + (Y & $C0) >> 4 + X/7 Simply calculating the Y portion requires 36 cycles or a 384 byte table, and the divide by 7 taking another 27 cycles. A good thing the Apple ][ had two pages of graphics so it could double buffer! But divide by 7? And where does this 6 colors per pixel come from? The two are related. To squeeze the highest resolution into the least memory possible, each pixel is only a single bit. Then the most significant bit in each byte is used as a color flag. But how is this used to generate six colors? The first color is simple, if the bit is 0 then the pixel is black. But if the bit is one then the fun begins. A pixel on an even column will be magenta if the MSB is 0 and blue if the MSB is 1. A pixel on an odd column will be green if the MSB is 0 and red if the MSB is 1. But if two lit pixels are horizontally adjacent then both pixels will be white. So really, the horizontal resolution is 280 pixels for black & white, but color horizontal resolution is only 140 pixels. This obviously causes some major headaches from a coders point of view. 0. high CPU or memory cost of address and pixel calculations 1. odd/even pixel bit position in the byte changes based on whether the byte is on an odd or even address 2. also the number of odd/even pixels per byte depends on whether the byte is on an odd or even address 3. white pixels can span multiple bytes and may occur unintentionally 4. overlapping colored sprites required masking to prevent interference (and I'm not certain some interferece can be completely avoided) 5. difficult/impossible to create truely multi-colored sprites (hmm, I think this also applies to #4) Again, I'm amazed of the ability of programmers to create some truely great games for the Apple ][ with all of these limitations and constraints.
lep0107.zip ( 13.12k )
: 7Nothing new* to see here unless you're looking at the source. Hopefully this is the final kernel rewrite. 1. reflected playfield 2. (zp),y player sprites 3. REFP = no separate left+right sprites 4. blank sprite instead of repositioning to far left What's interesting is even with this rewrite I effectively have no free cycles in the majority of the kernel. Or rather, there are free cycles, but they don't happen during the inactive portion of the display. There's even a minor color glitch on the far right side of the background every 18 lines because I had to put in the STA COLUPF early. (I could probably fix it with some reorganization, just not today.) And in the everything-is-related front, adding in REFP also took some work. As before, I shifted the XPOS left and changed the subtraction loop to 30 instead of 15, then tacked the REFP bit onto the HMP table. The problem I had before was that then messed up my "not used" indication of XPOS<0. So I attacked that on two fronts. First, in my PUTSPR routine I used COLUP=0 to indicate whether the sprite position had been used, then I added a final check which set the sprite pointer to a blank sprite when it wasn't used. I tried initially simply to make XPOS=255, over to the far right, but that created strange ghosts at the end of the level when I change sprite/playfield priorities. This way turned out to be simpler. The loop which writes the COLUP=0 and blank sprite pointers to SC RAM is also kinda interesting because I offset the Y value by one so the carry bit gets used for the loop. (If there was no Y offset, the loop would exit early when Y=0.) *Well the playfield will look different from the previous WIPs, but that's because I haven't adjuected the data for the reflected playfield yet.
Now that Christmas is over and I'm not trying to turn 700 slides into a DVD, I need to get back to working on Leprechaun. One item which is working (although I'm not going to post it since it doesn't affect gameplay) is the change to a reflected kernel. I wanted to get that working before the level editor sees the light of day. Kirk Israel has been working on the level editor, but the next challenge is implementing the save & load capability. My next update should include the corresponding level loader so people will be able to play their creations. I'm thinking that future WIPs will include two binaries - one which will play a built-in level, and a "developer edition" which will auto-load a created level. However, I'm thinking I should also give the player speedup / fractional positioning a shot in short order because that will shake up ZP RAM usage a fair bit, which also affects the level data. I also need to go back and do some code cleanup / optimization on stuff that's not likely to change. And I haven't even started to contemplate making some of the suggested radical changes to the kernel away from using BLITs to SC RAM for player bitmaps to a more traditional (ZP),Y ROM lookup. Then there's the stuff on my previous to-do list: Other major to-dos (not necessarily in this order): 1. Add the dig/fill logic. (DONE!) 2. Scoring w/ score & life display 3. Restarting the current level (reset gold) 4. Loading the next level 5. Enemy+Enemy collision detection. (Hmm, maybe bounce away?) 6. Multi-color sprites (depends mostly on cycles in the kernel) (nope, not enough cycles in the right places. 7. Title / end screens, music & periphrial support like the AtariVox and Glenn's joy2serial auto-load fob. [EDIT] #6
It's not a game unless you can keep score. The base idea I have is to have a count down timer for the level which would start each level at 999.9s or 16 min 39.9 sec. You would have unlimitted men (restarts) but limitted time. Bonus time would be given for picking up gold. Once you completed the level, your time remaining would be added to your total score. There will probably be the ability to practice levels, but that wouldn't be scored. How does that sound to everyone?
lep1215.zip ( 12.66k )
: 17The basic gameplay is now complete. Press the button and push the joystick right or left to dig. Comments, opinions & suggestions welcome. I also managed to trip myself up quite nicely as I was adding the new sprites. Basically I forgot that any access to $1FF8 aka $FFF8 on a SuperCharger will trigger a bankswitch. And I stuck the sprite data after the playfield bitmaps, which just happened to be on page $1F00. So the game would work quite nicely until it did the right blit and accessed $1FF8. Kaboom! This is one place where z26's trace log somes in handy since I had no idea what had happened. So I let z26 log until the game dies, then had a look at the last few frames in the trace file to try and figure out where things went amok. At first, I thought it was a rogue SC write which had trashed the code somehow. But later the real reason hit me. I was going to simply re-arrange the code & data to move the playfield bitmaps. But then I remembered my gold blit routine relies on the playfield bitmaps being at the end of the address space to simplify one of the loop tests. Sigh. So I've just temporarily shifted the offending spite to a free chunk of ROM. (I've just about used all 4K, although I'd always planned to use all 6K.) I think my next target is to rework the kernel to use a reflected playfield and do some other stuff to stabilize the level data format. (2005/12/15 - fixed some minor sprite glitches)
Yes, the WIP is almost done. I just need to add in the new digging sprites (and redo the old ones which I did by hand). Unfortunately, I'm going to have to find someplace to squeeze them in. I had this great idea about how to squeeze in the REFPn bit, but it didn't pan out. At one point I noticed that HMPn only uses the top 4 bits while REFPn uses bit 3. Ah-ha! I thought. If I multiply XPOS by two, change the SBC #15 in the repositioning routine to SBC #30, and double the HPOS table, I can make the LSB of XPOS be the left/right bit, and put the REFPn bit in the HPOS table. Cool! That means REFPn won't require an extra read, and there just happens to be 3 extra cycles in the current kernel which could be used to update REFPn. I even came up with a using AND/CMP/ROL to put the right/left bit into the XPOS LSB. Unfortunately, I'd forgotten that my repositioning routine assumes the sprite is unused if the MSB is set. So the sprites on the right side of the screen disappeared. Took me a bit to figure it out / remember. Sigh. So REFPn get postponed until the great kernel rewrite (not the reflection kernel, but removing the need for the forground bitmaps). Which, unfortunately, means I have to find 40 free bytes to squeeze in two sprites. Oh, I also loaded Leprechaun onto my CC2 (LEPRCH00 SC SC) for some real-life testing. One thing I immediately noticed (especially since I flipped the PF/player priority) was the green enemies were a little tough to see. The player is also a little dark. I'll try to remember to bump up the luma a little in this next WIP.
|
vdub_bobby on Leprechaun scoring idea
EricBall on TIA Schematic - Audio Noise Generator EricBall on TIA Schematic - Audio Noise Generator EricBall on TIA Schematic - Audio Noise Generator supercat on TIA Schematic - Audio Noise Generator supercat on Apple ][ hi-res graphics EricBall on Apple ][ hi-res graphics supercat on Apple ][ hi-res graphics EricBall on Apple ][ hi-res graphics supercat on Apple ][ hi-res graphics |
| Lo-Fi Version |
©2005 AtariAge |
Time is now: Thu Feb 2, 2006 5:29 AM
Contact | Privacy Policy | Legal |